﻿using System.Linq;
using Microsoft.Practices.Unity;
using System;
using Microsoft.Practices.Unity.Configuration;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.Web.Configuration;
using Castle.Windsor;
using Castle.Windsor.Configuration.Interpreters;

namespace MvcEngine.Core
{
    /// <summary>
    /// IoC which will manage the instantiation of classes by using Unity as a dependency injection tool.
    /// The registration of types will take place outside this class, but the methods RegisterInstance and RegisterType 
    /// can be used to accomplish this.
    /// The Dependency injection container is shared between all thread since it is stored as an static property. The lifetime of the container is the same as the lifetime for the IoC instance.
    /// </summary>
    public static class IoC
    {
        /// <summary>
        /// Container which holds the registered types and the mappings to the objects.
        /// </summary>
        private static IUnityContainer Container;
        private static TypeTrackingExtension extension;

        /// <summary>
        /// Call this method to initialize the dependency injection container, which will hold the registered types.
        /// </summary>
        public static void Initialize()
        {
            // create and populate a new Unity container from configuration
            Initialize(new UnityContainer(), true);
            ResolveConfigurationFile();
        }

        /// <summary>
        /// Call this method to initialize the dependency injection container with a custom container, which will hold the registered types.
        /// </summary>
        public static void Initialize(IUnityContainer customContainer, bool useConfigurationFile)
        {
            // create and populate a new Unity container from configuration
            Container = customContainer;
            extension = new TypeTrackingExtension();
            Container.AddExtension(extension);
            if (useConfigurationFile)
            {
                ResolveConfigurationFile();
            }
        }

        private static void ResolveConfigurationFile() 
        {
            UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
            section.Containers.Default.Configure(Container);
        }

        public static void ResolveConfigurationFile(string virtualPath)
        {
            UnityConfigurationSection section = (UnityConfigurationSection)WebConfigurationManager.OpenWebConfiguration(virtualPath).GetSection("unity");
            for (int i = 0; i < section.Containers.Count; i++)
            {
                
                section.Containers[i].Configure(Container);    
            }
        }



        /// <summary>
        /// Register an instance in the dependency injection container.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="instance"></param>
        public static void RegisterInstance<T>(T instance)
        {
            EnsureInitialized();
            Container.RegisterInstance<T>(instance);
        }

        /// <summary>
        /// Register the named instance in the dependency injection container.
        /// </summary>
        /// <typeparam name="T">The injected type.</typeparam>
        /// <param name="name">The instance name.</param>
        /// <param name="instance">The instance.</param>
        public static void RegisterInstance<T>(string name, T instance)
        {
            EnsureInitialized();
            Container.RegisterInstance<T>(name, instance);
        }

        /// <summary>
        /// Register a typemapping in the dependency injection container.
        /// <example>
        /// <code>
        ///   IoC.Initialize();
        ///   IoC.RegisterType{ICatalogManager, CatalogManager}();
        /// </code>
        /// </example>
        /// </summary>
        /// <typeparam name="I">Type that will be requested.</typeparam>
        /// <typeparam name="T">Type that will actually be returned.</typeparam>
        public static InjectedObject<T> RegisterType<I, T>() where T : I
        {
            EnsureInitialized();
            Container.RegisterType<I, T>();
            return new InjectedObject<T>();
        }

        public static InjectedObject RegisterType(Type from, Type to, string name, LifetimeManager lifetimeManager)
        {
            EnsureInitialized();
            Container.RegisterType(from, to, name, lifetimeManager);
            return new InjectedObject(to);
        }

        

        /// <summary>
        /// Register a typemapping in the dependency injection container.
        /// </summary>
        /// <param name="from">Type that will be requested.</param>
        /// <param name="to">Type that will actually be returned.</param>
        public static InjectedObject RegisterType(Type from, Type to)
        {
            EnsureInitialized();
            Container.RegisterType(from, to);
            return new InjectedObject(to);
        }

        /// <summary>
        /// Register a typemapping in the dependency injection container.
        /// </summary>
        /// <param name="from">Type that will be requested.</param>
        /// <param name="to">Type that will actually be returned.</param>
        /// <param name="name">The logical name of the registration.</param>
        public static InjectedObject RegisterType(Type from, Type to, string name)
        {
            EnsureInitialized();
            Container.RegisterType(from, to, name);
            return new InjectedObject(to);
        }

        /// <summary>
        /// Register a typemapping in the dependency injection container.
        /// </summary>
        /// <typeparam name="I">Type that will be requested.</typeparam>
        /// <typeparam name="T">Type that will actually be returned.</typeparam>
        /// <param name="name">The logical name of the registration.</param>
        public static InjectedObject<T> RegisterType<I, T>(string name) where T : I
        {
            EnsureInitialized();
            Container.RegisterType<I, T>(name);
            return new InjectedObject<T>();
        }

        /// <summary>
        /// Registers a type mapping for a singleton type in the dependency injection container.
        /// </summary>
        /// <typeparam name="I">Type that will be requested.</typeparam>
        /// <typeparam name="T">Type that will actually be returned.</typeparam>
        /// <returns>Returns an instance of the <see cref="InjectedObject&lt;T&gt;"/>.</returns>
        public static InjectedObject<T> RegisterSingletonType<I, T>() where T : I
        {
            EnsureInitialized();
            Container.RegisterType<I, T>(new ContainerControlledLifetimeManager());
            return new InjectedObject<T>();
        }

        /// <summary>
        /// Registers a named type mapping for a singleton type in the dependency injection container.
        /// </summary>
        /// <typeparam name="I">Type that will be requested.</typeparam>
        /// <typeparam name="T">Type that will actually be returned.</typeparam>
        /// <param name="name">The registration name.</param>
        /// <returns>Returns an instance of the <see cref="InjectedObject&lt;T&gt;"/>.</returns>
        public static InjectedObject<T> RegisterSingletonType<I, T>(string name) where T : I
        {
            EnsureInitialized();
            Container.RegisterType<I, T>(name, new ContainerControlledLifetimeManager());
            return new InjectedObject<T>();
        }

        /// <summary>
        /// Determines whether this instance for the specified type is registered.
        /// </summary>
        /// <typeparam name="T">The injected type.</typeparam>
        /// <returns>
        /// 	<see langword="true"/> if this instance for the specified type is registered; otherwise, <see langword="false"/>.
        /// </returns>
        public static bool IsRegistered<T>()
        {
            EnsureInitialized();
            return extension.IsRegisteredType(typeof(T), TypeTrackingExtension.DefaultName) || 
                extension.IsRegisteredInstance(typeof(T), TypeTrackingExtension.DefaultName);
        }

        /// <summary>
        /// Determines whether this instance for the specified type is registered.
        /// </summary>
        /// <typeparam name="T">The injected type.</typeparam>
        /// <returns>
        /// 	<see langword="true"/> if this instance for the specified type is registered; otherwise, <see langword="false"/>.
        /// </returns>
        public static bool IsRegistered<T>(string name)
        {
            EnsureInitialized();
            return extension.IsRegisteredType(typeof(T), name) || extension.IsRegisteredInstance(typeof(T), name);
        }

        /// <summary>
        /// Returns an instance of the requested type.
        /// If the requested type is not registered an exception is thrown.
        /// </summary>
        /// <example>
        /// <code>
        ///   IoC.Initialize();
        ///   IoC.RegisterType{ICatalogManager, CatalogManager}();
        ///   
        ///   //This call will return the correct inplementation.
        ///   ICatalogManager manager = IoC.GetInstance{ICatalogManager}();
        /// </code>
        /// </example>
        /// <typeparam name="T">The requested type for which an instance will be returned.</typeparam>
        /// <returns>an instance of the requested type</returns>
        public static T GetInstance<T>()
        {
            EnsureInitialized();
            return Container.Resolve<T>();
        }

        public static object GetInstance(Type type)
        {
            EnsureInitialized();
            return Container.Resolve(type);
        }


        public static IEnumerable<T> GetAllInstances<T>()
        {
            EnsureInitialized();
            return Container.ResolveAll<T>();
        }


        /// <summary>
        /// Returns an instance of the requested type.
        /// If the requested type is not registered an exception is thrown.
        /// <example>
        /// <code>
        ///   IoC.Initialize();
        ///   
        ///   //Calls to interface IProductFacetService using unity will now return concrete instance of ProductFacetService
        ///   IoC.RegisterType&lt;IProductFacetService, ProductFacetService&gt;();
        ///   
        ///   //This call will return the correct inplementation.
        ///   ProductFacetService facets = IoC.GetInstance&lt;IProductFacetService&gt;();
        /// </code>
        /// </example>
        /// </summary>
        /// <typeparam name="T">The requested type for which an instance will be returned.</typeparam>
        /// <param name="name">The name of the registration.</param>
        /// <returns>an instance of the requested type</returns>
        public static T GetInstance<T>(string name)
        {
            EnsureInitialized();
            if (!String.IsNullOrEmpty(name))
                return Container.Resolve<T>(name);
            else
                return Container.Resolve<T>();
        }

        /// <summary>
        /// Set the constructor that will be used when this object is created by unity.
        /// <example>
        /// <code>
        /// IoC.Initialize();
        /// 
        /// //Set the default constructor to ProviderConfigurationManager("CatalogProvider") for objects created of the IProviderConfigurationManager object.
        /// IoC.SetDefaultConstructor{IProviderConfigurationManager}("CatalogProvider");
        /// </code>
        /// </example>
        /// </summary>    
        /// <typeparam name="T">Type to set the constructor for.</typeparam>
        /// <param name="parameterValues">Arguments to pass to the constructor, must match the number and types of parameters of the constructor to use.</param>
        public static void SetDefaultConstructor<T>(params object[] parameterValues)
        {
            EnsureInitialized();
            if (parameterValues != null && parameterValues.Count() > 0)
                Container.Configure<InjectedMembers>().ConfigureInjectionFor<T>(new InjectionConstructor(parameterValues));
            else
                Container.Configure<InjectedMembers>().ConfigureInjectionFor<T>(new InjectionConstructor());
        }

        /// <summary>
        /// Set the constructor that will be used when this object is created by unity.
        /// <example>
        /// <code>
        /// IoC.Initialize();
        /// 
        /// //Set the default constructor to ProviderConfigurationManager("CatalogProvider") for objects created of the IProviderConfigurationManager object.
        /// IoC.SetDefaultConstructor{IProviderConfigurationManager}("CatalogProvider");
        /// </code>
        /// </example>
        /// </summary>    
        /// <typeparam name="T">Type to set the constructor for.</typeparam>
        /// <param name="name">The logical name of the injection to configure constructor for.</param>
        /// <param name="parameterValues">Arguments to pass to the constructor, must match the number and types of parameters of the constructor to use.</param>
        public static void SetDefaultConstructor<T>(string name, params object[] parameterValues)
        {
            EnsureInitialized();
            if (parameterValues != null && parameterValues.Count() > 0)
                Container.Configure<InjectedMembers>().ConfigureInjectionFor<T>(name, new InjectionConstructor(parameterValues));
            else
                Container.Configure<InjectedMembers>().ConfigureInjectionFor<T>(name, new InjectionConstructor());
        }

        /// <summary>
        /// Set the constructor that will be used when this object is created by unity.
        /// <example>
        /// <code>
        /// IoC.Initialize();
        /// 
        /// //Set the default constructor to ProviderConfigurationManager("CatalogProvider") for objects created of the IProviderConfigurationManager object.
        /// IoC.SetDefaultConstructor(typeof(IProviderConfigurationManager), "CatalogProvider");
        /// </code>
        /// </example>
        /// </summary>    
        /// <param name="type">The injected type to set default constructor for.</param>
        /// <param name="parameterValues">Arguments to pass to the constructor, must match the number and types of parameters of the constructor to use.</param>
        public static void SetDefaultConstructor(Type type, params object[] parameterValues)
        {
            EnsureInitialized();
            if (parameterValues != null && parameterValues.Count() > 0)
                Container.Configure<InjectedMembers>().ConfigureInjectionFor(type, new InjectionConstructor(parameterValues));
            else
                Container.Configure<InjectedMembers>().ConfigureInjectionFor(type, new InjectionConstructor());
        }

        /// <summary>
        /// Set the constructor that will be used when this object is created by unity.
        /// <example>
        /// <code>
        /// IoC.Initialize();
        /// //Set the default constructor to ProviderConfigurationManager("CatalogProvider") for objects created of the IProviderConfigurationManager object.
        /// IoC.SetDefaultConstructor(typeof(IProviderConfigurationManager), "Default", "CatalogProvider");
        /// </code>
        /// </example>
        /// </summary>    
        /// <param name="type">The injected type to set default constructor for.</param>
        /// <param name="name">The logical name of the injection to configure constructor for.</param>
        /// <param name="parameterValues">Arguments to pass to the constructor, must match the number and types of parameters of the constructor to use.</param>
        public static void SetDefaultConstructor(Type type, string name, params object[] parameterValues)
        {
            EnsureInitialized();
            if (parameterValues != null && parameterValues.Count() > 0)
                Container.Configure<InjectedMembers>().ConfigureInjectionFor(type, name, new InjectionConstructor(parameterValues));
            else
                Container.Configure<InjectedMembers>().ConfigureInjectionFor(type, name, new InjectionConstructor());
        }

        /// <summary>
        /// Disposes the dependency injection container.
        /// </summary>
        public static void Dispose()
        {
            if (Container == null) return;
            Container.RemoveAllExtensions();
            Container.Dispose();
            Container = null;
        }

        /// <summary>
        /// Shortcut to get the current registered type from unity
        /// </summary>
        /// <typeparam name="TEntity">The entity to get the type for</typeparam>
        /// <returns></returns>
        public static System.Type GetRegisteredType<TEntity>()
        {
            EnsureInitialized();
            return extension.GetRegisteredType(typeof(TEntity), TypeTrackingExtension.DefaultName);
        }
        
        /// <summary>
        /// Ensures that container has been initialized.
        /// </summary>
        private static void EnsureInitialized()
        {
            if (Container == null)
                Initialize();
        }
    }

    /// <summary>
    /// Injected object is an object of type T that was just registered with the unity framework. This makes a chaining API
    /// possible.
    /// </summary>
    /// <example>
    /// <code>
    /// IoC.Initialize();
    /// IoC.RegisterType&lt;IProviderConfigurationManager, ProviderConfigurationManager&gt;().SetDefaultConstructor("CatalogProvider");
    /// </code>
    /// </example>     
    /// <typeparam name="T">The type of the object registered</typeparam>
    public class InjectedObject<T> : InjectedObject
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="InjectedObject&lt;T&gt;"/> class.
        /// </summary>
        public InjectedObject()
            : base(typeof(T))
        {
        }
    }

    /// <summary>
    /// Injected object is an object of some type that was just registered with the unity framework. This makes a chaining API
    /// possible.
    /// </summary>
    /// <example>
    /// <code>
    /// IoC.Initialize();
    /// IoC.RegisterType(type1, type2).SetDefaultConstructor("CatalogProvider");
    /// </code>
    /// </example>     
    public class InjectedObject
    {
        private Type type;

        /// <summary>
        /// Initializes a new instance of the <see cref="InjectedObject"/> class.
        /// </summary>
        /// <param name="registeredType">The registered type.</param>
        public InjectedObject(Type registeredType)
        {
            this.type = registeredType;
        }

        /// <summary>
        /// Sets the default constructor of the registered type.
        /// </summary>
        /// <param name="parameterValues">The constructor parameter values.</param>
        public void SetDefaultConstructor(params object[] parameterValues)
        {
            IoC.SetDefaultConstructor(type, parameterValues);
        }
    }
}
